<template>
  <div class="pro-upload">
    <el-upload
      v-model:file-list="fileList"
      :action="action"
      :headers="headers"
      :data="data"
      :name="name"
      :accept="accept"
      :limit="multiple ? limit : 1"
      :disabled="disabled"
      :multiple="multiple"
      :drag="drag"
      :before-upload="handleBeforeUpload"
      :on-success="handleSuccess"
      :on-error="handleError"
      :on-exceed="handleExceed"
      :on-remove="handleRemove"
    >
      <!-- 单图片上传 -->
      <template v-if="type === 'image' && !multiple">
        <div v-if="!fileList.length" class="image-uploader">
          <el-icon class="image-uploader-icon"><Plus /></el-icon>
        </div>
        <img v-else :src="modelValue" class="image-preview" />
      </template>

      <!-- 拖拽上传 -->
      <template v-else-if="drag">
        <el-icon class="el-icon--upload"><Upload /></el-icon>
        <div class="el-upload__text">将文件拖到此处或<em>点击上传</em></div>
      </template>

      <!-- 普通上传 -->
      <template v-else>
        <el-button :type="buttonType" :icon="Upload">
          {{ buttonText }}
        </el-button>
      </template>

      <!-- 提示文字 -->
      <template #tip>
        <div class="upload-tip">{{ tip }}</div>
      </template>
    </el-upload>

    <!-- 图片裁剪对话框 -->
    <el-dialog v-model="cropperVisible" title="图片裁剪" width="600px">
      <div class="cropper-container">
        <vue-cropper
          ref="cropperRef"
          :src="cropperImage"
          :aspect-ratio="cropperRatio"
          :auto-crop="true"
          :center="true"
          :info="true"
          :full="true"
          :fixed="cropperFixed"
          :fixed-number="cropperFixedNumber"
        />
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="cropperVisible = false">取消</el-button>
          <el-button type="primary" @click="handleCropperConfirm"
            >确定</el-button
          >
        </div>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import { ref, watch } from "vue";
import { Plus, Upload } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import type {
  UploadFile,
  UploadFiles,
  UploadUserFile,
  UploadInstance,
} from "element-plus";
import VueCropper from "vue-cropper";
import "vue-cropper/dist/index.css";

interface Props {
  modelValue?: string | string[];
  action: string;
  headers?: Record<string, string>;
  data?: Record<string, any>;
  name?: string;
  accept?: string;
  multiple?: boolean;
  limit?: number;
  disabled?: boolean;
  drag?: boolean;
  type?: "file" | "image";
  buttonType?: string;
  buttonText?: string;
  tip?: string;
  maxSize?: number;
  crop?: boolean;
  cropperRatio?: number;
  cropperFixed?: boolean;
  cropperFixedNumber?: [number, number];
}

interface Emits {
  (e: "update:modelValue", value: string | string[]): void;
  (e: "success", response: any, file: UploadUserFile): void;
  (e: "error", error: Error, file: UploadUserFile): void;
  (e: "exceed", files: File[], uploadFiles: UploadUserFile[]): void;
  (e: "remove", file: UploadUserFile): void;
}

const props = withDefaults(defineProps<Props>(), {
  headers: () => ({}),
  data: () => ({}),
  name: "file",
  multiple: false,
  limit: 5,
  disabled: false,
  drag: false,
  type: "file",
  buttonType: "primary",
  buttonText: "点击上传",
  maxSize: 10,
  crop: false,
  cropperRatio: 1,
  cropperFixed: false,
  cropperFixedNumber: () => [1, 1],
});

const emit = defineEmits<Emits>();

// 文件列表
const fileList = ref<UploadFile[]>([]);

// 监听 modelValue 变化
watch(
  () => props.modelValue,
  (val) => {
    if (!val) {
      fileList.value = [];
    } else if (Array.isArray(val)) {
      fileList.value = val.map((url) => ({
        name: url.split("/").pop() || "",
        url,
      }));
    } else {
      fileList.value = [
        {
          name: val.split("/").pop() || "",
          url: val,
        },
      ];
    }
  },
  { immediate: true }
);

// 裁剪相关
const cropperRef = ref();
const cropperVisible = ref(false);
const cropperImage = ref("");
const currentFile = ref<File>();

// 上传前处理
const handleBeforeUpload = (file: File) => {
  const isLtSize = file.size / 1024 / 1024 < props.maxSize;
  if (!isLtSize) {
    ElMessage.error(`文件大小不能超过 ${props.maxSize}MB!`);
    return false;
  }

  if (props.type === "image" && props.crop && file.type.startsWith("image/")) {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => {
      cropperImage.value = reader.result as string;
      cropperVisible.value = true;
      currentFile.value = file;
    };
    return false;
  }

  return true;
};

// 上传成功
const handleSuccess = (response: any, file: UploadUserFile) => {
  emit("success", response, file);
  updateModelValue();
};

// 上传失败
const handleError = (error: Error, file: UploadUserFile) => {
  emit("error", error, file);
};

// 超出限制
const handleExceed = (files: File[], uploadFiles: UploadUserFile[]) => {
  ElMessage.warning(`最多只能上传 ${props.limit} 个文件`);
  emit("exceed", files, uploadFiles);
};

// 移除文件
const handleRemove = (file: UploadUserFile) => {
  emit("remove", file);
  updateModelValue();
};

// 更新绑定值
const updateModelValue = () => {
  if (props.multiple) {
    emit(
      "update:modelValue",
      fileList.value.map((file) => file.url || "")
    );
  } else {
    emit(
      "update:modelValue",
      fileList.value.length ? fileList.value[0].url || "" : ""
    );
  }
};

// 确认裁剪
const handleCropperConfirm = () => {
  if (!cropperRef.value || !currentFile.value) return;

  cropperRef.value.getCropBlob((blob: Blob) => {
    const file = new File([blob], currentFile.value!.name, {
      type: currentFile.value!.type,
    });
    const formData = new FormData();
    formData.append(props.name, file);
    // TODO: 调用上传接口
    cropperVisible.value = false;
  });
};
</script>

<style lang="scss" scoped>
.pro-upload {
  .image-uploader {
    width: 178px;
    height: 178px;
    border: 1px dashed var(--el-border-color);
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    transition: var(--el-transition-duration-fast);

    &:hover {
      border-color: var(--el-color-primary);
    }

    .image-uploader-icon {
      font-size: 28px;
      color: #8c939d;
      width: 178px;
      height: 178px;
      text-align: center;
      display: flex;
      justify-content: center;
      align-items: center;
    }
  }

  .image-preview {
    width: 178px;
    height: 178px;
    display: block;
  }

  .upload-tip {
    font-size: 12px;
    color: #606266;
    margin-top: 7px;
  }

  .cropper-container {
    height: 400px;
  }
}
</style>
